package no.gnet.edvd.web.controller;

import static no.gnet.edvd.web.util.Constants.ALLEVENTS;
import static no.gnet.edvd.web.util.Constants.EVENTODDSMAP_;
import static no.gnet.edvd.web.util.Constants.EVENTOPTION_PREFIX;
import static no.gnet.edvd.web.util.Constants.SESSIONUSER;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import no.gnet.edvd.DataStore;
import no.gnet.edvd.engines.Engine;
import no.gnet.edvd.engines.EventOdds;
import no.gnet.edvd.lightweight.CompactBet;
import no.gnet.edvd.lightweight.PotentialOdds;
import no.gnet.edvd.statemachine.BetState;
import no.gnet.edvd.types.ActivityType;
import no.gnet.edvd.types.Bet;
import no.gnet.edvd.types.Currency;
import no.gnet.edvd.types.Event;
import no.gnet.edvd.types.Option;
import no.gnet.edvd.types.PaymentAccount;
import no.gnet.edvd.types.Player;
import no.gnet.edvd.types.StringOption;
import no.gnet.edvd.types.Transfer;
import no.gnet.edvd.types.Wallet;
import no.gnet.edvd.web.cache.MemCache;
import no.gnet.edvd.web.types.EdvdSessionUser;
import no.gnet.edvd.web.types.exception.IllegalActionException;
import no.gnet.edvd.web.types.exception.IllegalBetStateException;
import no.gnet.edvd.web.types.exception.NegativeAmountException;
import no.gnet.edvd.web.types.exception.NotEnoughCreditException;
import no.gnet.edvd.web.types.exception.NoValidAccountException;
import no.gnet.edvd.web.types.exception.ToohighAmountException;

import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.view.RedirectView;

@Controller
public class BetController extends CommonController {

	private static final String BETS_CACHE_PREFIX = "bets_";

	private static final String USER_BETS_CACHE_PREFIX = "userbets_";

	private static final int NON_GREEN_LIMIT = 10;

	private static final int GREEN_LIMIT = 100;

	Player testplayer = new Player("jimmy", "jimmyson", "jimmy@jimson.com");

	static Logger logger = Logger.getLogger(BetController.class);

	@Autowired
	Engine engine;

	@Autowired
	DataStore datastore;

	@RequestMapping(value = "/welcome", method = RequestMethod.GET)
	public String printWelcome(ModelMap model, HttpServletRequest request) {
		List<EventDescriptor> eventDescriptors = refreshEventDescriptors(
				datastore, engine, request);
		model.addAttribute(ALLEVENTS, eventDescriptors);
		model.addAttribute(SESSIONUSER, sessionUser(request));
		return "hello";
	}

	@RequestMapping(value = "/angular", method = RequestMethod.GET)
	public String angular() {
		return "angular";
	}

	@SuppressWarnings("unchecked")
	@RequestMapping(value = "/api/ownevents", method = RequestMethod.GET)
	public @ResponseBody
	List<Event> getOwnEventDescriptors(HttpServletRequest request) {
		EdvdSessionUser user = sessionUser(request);
		if (user == null || user.getPlayer() == null) {
			logger.warn("no user");
			return null;
		}
		List<Event> myevents = datastore.getEventsOwnedBy(user.getPlayer().id);
		logger.info("Returning number of own events: " + myevents.size());
		return myevents;
	}

	@RequestMapping(value = "/api/mybets", method = RequestMethod.GET)
	public @ResponseBody
	List<Bet> getMyBets(HttpServletRequest request) {
		EdvdSessionUser user = sessionUser(request);
		if (user == null || user.getPlayer() == null) {
			logger.warn("no user");
			return null;
		}
		int pid = user.getPlayer().id;
		logger.info("Getting bets for player " + pid);
		String cachekey = USER_BETS_CACHE_PREFIX + pid;
		List<Bet> bets = (List<Bet>) MemCache.get(cachekey);
		if (bets == null) {
			bets = datastore.getAllBetsForPlayer(pid);
			MemCache.put(cachekey, bets);
		}
		return bets;

	}

	@RequestMapping(value = "/register", method = RequestMethod.GET)
	public String register(ModelMap model,
			@RequestParam("s") int numberofoptions, HttpServletRequest request) {
		model.addAttribute("numberofoptions", numberofoptions);
		model.addAttribute(SESSIONUSER, sessionUser(request));
		return "registerevent";
	}

	@SuppressWarnings("unchecked")
	@RequestMapping(value = "/api/bets/{eventid}", method = RequestMethod.GET)
	public @ResponseBody
	List<Bet> getBetsForEvent(@PathVariable String eventid) {
		logger.info("Getting bets for event " + eventid);
		int iid = Integer.parseInt(eventid);
		List<Bet> bets = (List<Bet>) MemCache.get(BETS_CACHE_PREFIX + eventid);
		if (bets == null) {
			bets = datastore.getBetsForEvent(iid);
			MemCache.put(BETS_CACHE_PREFIX + eventid, bets);
		}
		return bets;
	}


	private List<Option> getOptions(Map<String, String> allRequestParams) {
		ArrayList<Option> options = new ArrayList<Option>();
		int counter = 1;
		boolean optionsLeft = true;
		while (optionsLeft) {
			String optionInput = allRequestParams.get(EVENTOPTION_PREFIX
					+ counter);
			if (optionInput != null) {
				options.add(new StringOption(optionInput));
				counter++;
			} else {
				optionsLeft = false;
			}
		}
		return options;
	}

	@RequestMapping(value = "/placebet", method = RequestMethod.GET)
	public String placeBet(ModelMap model, @RequestParam("id") int optionid,
			HttpServletRequest request) {
		Option option = datastore.getOption(optionid);
		Event event = option.event;
		model.addAttribute("option", option);
		model.addAttribute("event", event);
		model.addAttribute(SESSIONUSER, sessionUser(request));
		return "confirmBet";
	}

	@RequestMapping(value = "/addBet", method = RequestMethod.POST, produces = "application/json", headers = { "Content-type=application/json" })
	public @ResponseBody
	CompactBet addBet(@RequestBody CompactBet compactBet,
			HttpServletRequest request) throws ServletException {
		Player player = sessionUser(request).getPlayer();
		Bet abet = assertBetOk(compactBet, request,player);
		datastore.placeBet(abet);
		String text = "Player placed bet, event is "+abet.getEvent().getId()+", option is "+abet.getOption().id+", amount is "+abet.amount;
		datastore.logActivity(ActivityType.BET_PLACED,text , player);
		MemCache.markDirty(SINGLE_EVENT_CACHE_PREFIX + abet.event.id);
		MemCache.markDirty(EVENTODDSMAP_ + abet.event.id);
		MemCache.markDirty(BETS_CACHE_PREFIX + abet.event.id);
		return compactBet;
	}
	
	
	

	@RequestMapping(value = "/whatif", method = RequestMethod.POST, produces = "application/json", headers = { "Content-type=application/json" })
	public @ResponseBody
	PotentialOdds whatif(@RequestBody CompactBet compactBet,
			HttpServletRequest request){
		logger.info("what if, bet is " + compactBet);
		Player player = sessionUser(request).getPlayer();
		Bet abet = assertBetOk(compactBet, request,player);
		EventOdds eventodds = engine.whatIf(abet.event, abet, datastore);
		double potentialO = eventodds.get(abet.getOption());
		PotentialOdds po = new PotentialOdds(potentialO,(abet.amount*potentialO));
		String text = "Player whatif'ed bet, event is "+abet.getEvent().getId()+", option is "+abet.getOption().id+", amount is "+abet.amount;
		datastore.logActivity(ActivityType.BET_WHATIF,text , player);
		return po;
	}
	
	
	private Bet assertBetOk(CompactBet compactBet, HttpServletRequest request, Player player) {
		if (sessionUser(request) == null) {
			throw new IllegalActionException("User not allowed to bet");
		}
		Bet abet = createBetFromCompact(compactBet, player);
		
		if (noValidAccount(sessionUser(request))) {
			throw new NoValidAccountException(
					"User must have associated paying account");
		}

		if(!playerIsGreen(player)){
			if(compactBet.getAmount()>NON_GREEN_LIMIT){
				throw new ToohighAmountException("Cannot bet more than " + NON_GREEN_LIMIT+" when status is not green");
			}
		} else {
			if(compactBet.getAmount()>GREEN_LIMIT){
				throw new ToohighAmountException("Cannot bet more than limit. Limit is " + GREEN_LIMIT);
			}
		}
		
		if(indebt(sessionUser(request)))
			throw new NotEnoughCreditException("User has outstanding transfers");
		
		if (abet.amount < 0) {
			throw new NegativeAmountException("Cannot bet negative amount..");
		}
		
		if (!abet.event.state.equals(BetState.OPEN_FOR_BETTING)) {
			throw new IllegalBetStateException("This event is not open for betting");
		}
		return abet;
	}

	private boolean playerIsGreen(Player player) {
		List<Bet> allBetsForPlayer = datastore.getAllBetsForPlayer(player.id);
		int settled=0;
		for(Bet bet : allBetsForPlayer){
			if(bet.getEvent().getState().equals(BetState.SETTLED)){
				settled++;
			}
		}
		boolean status = settled >= 5;
		List<Transfer> unsettledTransfersForUser = datastore.getUnsettledTransfersForUser(player.id);
		if(unsettledTransfersForUser!=null && unsettledTransfersForUser.size()>0){
			status =false;
		}
		logger.info("Playerstatus is green: " + status +" for player " + player.getEmail());
		return status;
	}

	private boolean indebt(EdvdSessionUser sessionUser) {
		return sessionUser.getUnpaidTransfers()!=null && sessionUser.getUnpaidTransfers().size()>0;
	}

	private boolean noValidAccount(EdvdSessionUser sessionUser) {
		PaymentAccount account = datastore.getPaymentAccountForUser(sessionUser.getPlayer().id);
		return account==null;
	}

	private boolean cannotAfford(EdvdSessionUser sessionUser, double amount) {
		Wallet wallet = datastore.getWalletForUser(sessionUser.getPlayer().id);
		boolean notenough = wallet == null || wallet.amount < amount;
		return notenough;
	}

	private Bet createBetFromCompact(CompactBet compactBet, Player player) {
		Option option = datastore.getOption(compactBet.getOptionid());
		Event event = datastore.getBettableEvent(option.event_id);
		Bet bet = new Bet(event, option, compactBet.getAmount(),
				compactBet.getCurrency(), player, new DateTime());

		return bet;
	}

	@RequestMapping(value = "/confirmPlaceBet", method = RequestMethod.POST)
	public RedirectView confirmPlaceBet(ModelMap model,
			@RequestParam("id") int optionid,
			@RequestParam("amount") int amount, HttpServletRequest request) {
		Option option = datastore.getOption(optionid);
		Event event = option.event;
		EdvdSessionUser user = sessionUser(request);
		if (user == null) {
			model.addAttribute(ERRORMESSAGE, "You need to log on");
			return new RedirectView("errorpage");
		}
		Bet bet = new Bet(event, option, amount, Currency.NOK,
				user.getPlayer(), new DateTime());
		datastore.placeBet(bet);
		model.addAttribute("option", option);
		model.addAttribute("event", event);
		return new RedirectView("event?id=" + event.getId());
	}

	@RequestMapping(value = "/errorpage", method = RequestMethod.GET)
	public String errorpage(ModelMap model, HttpServletRequest request) {
		model.addAttribute(ERRORMESSAGE, request.getParameter(ERRORMESSAGE));
		return "errorpage";
	}
}
